namespace Community.CsharpSqlite
{
    public class Complete
    {
        /*
    ** 2001 September 15
    **
    ** The author disclaims copyright to this source code.  In place of
    ** a legal notice, here is a blessing:
    **
    **    May you do good and not evil.
    **    May you find forgiveness for yourself and forgive others.
    **    May you share freely, never taking more than you give.
    **
    *************************************************************************
    ** An tokenizer for SQL
    **
    ** This file contains C code that implements the sqlite3_complete() API.
    ** This code used to be part of the tokenizer.c source file.  But by
    ** separating it out, the code will be automatically omitted from
    ** static links that do not use it.
    *************************************************************************
    **  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
    **  C#-SQLite is an independent reimplementation of the SQLite software library
    **
    **  SQLITE_SOURCE_ID: 2010-03-09 19:31:43 4ae453ea7be69018d8c16eb8dabe05617397dc4d
    **
    **  $Header: Community.CsharpSqlite/src/complete_c.cs,v 6604176a7dbe 2010/03/12 23:35:36 Noah $
    *************************************************************************
    */
        //#include "sqliteInt.h"
#if !SQLITE_OMIT_COMPLETE

        /*
** This is defined in tokenize.c.  We just have to import the definition.
*/
#if !SQLITE_AMALGAMATION
#if  SQLITE_ASCII
        //#define IdChar(C)  ((Global.Sqlite3CtypeMap[(unsigned char)C]&0x46)!=0)
        public static bool IdChar(byte C)
        {
            return (Global.Sqlite3CtypeMap[(char) C] & 0x46) != 0;
        }
#endif
#if  SQLITE_EBCDIC
//extern const char sqlite3IsEbcdicIdChar[];
//#define IdChar(C)  (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40]))
#endif
#endif
        // * SQLITE_AMALGAMATION */


        /*
** Token types used by the sqlite3_complete() routine.  See the header
** comments on that procedure for additional information.
*/
        private const int tkSEMI = 0;
        private const int tkWS = 1;
        private const int tkOTHER = 2;
#if !SQLITE_OMIT_TRIGGER
        private const int tkEXPLAIN = 3;
        private const int tkCREATE = 4;
        private const int tkTEMP = 5;
        private const int tkTRIGGER = 6;
        private const int tkEND = 7;
#endif


        /// <summary>
        ///  Return TRUE if the given SQL string ends in a semicolon.
        /// 
        ///  Special handling is require for CREATE TRIGGER statements.
        ///  Whenever the CREATE TRIGGER keywords are seen, the statement
        ///  must end with ";END;".
        /// 
        ///  This implementation uses a state machine with 8 states:
        /// 
        ///    (0) INVALID   We have not yet seen a non-whitespace character.
        /// 
        ///    (1) START     At the beginning or end of an SQL statement.  This routine
        ///                  returns 1 if it ends in the START state and 0 if it ends
        ///                  in any other state.
        /// 
        ///    (2) NORMAL    We are in the middle of statement which ends with a single
        ///                  semicolon.
        /// 
        ///    (3) EXPLAIN   The keyword EXPLAIN has been seen at the beginning of
        ///                  a statement.
        /// 
        ///    (4) CREATE    The keyword CREATE has been seen at the beginning of a
        ///                  statement, possibly preceeded by EXPLAIN and/or followed by
        ///                  TEMP or TEMPORARY
        /// 
        ///    (5) TRIGGER   We are in the middle of a trigger definition that must be
        ///                  ended by a semicolon, the keyword END, and another semicolon.
        /// 
        ///    (6) SEMI      We've seen the first semicolon in the ";END;" that occurs at
        ///                  the end of a trigger definition.
        /// 
        ///    (7) END       We've seen the ";END" of the ";END;" that occurs at the end
        ///                  of a trigger difinition.
        /// 
        ///  Transitions between states above are determined by tokens extracted
        ///  from the input.  The following tokens are significant:
        /// 
        ///    (0) tkSEMI      A semicolon.
        ///    (1) tkWS        Whitespace.
        ///    (2) tkOTHER     Any other SQL token.
        ///    (3) tkEXPLAIN   The "explain" keyword.
        ///    (4) tkCREATE    The "create" keyword.
        ///    (5) tkTEMP      The "temp" or "temporary" keyword.
        ///    (6) tkTRIGGER   The "trigger" keyword.
        ///    (7) tkEND       The "end" keyword.
        /// 
        ///  Whitespace never causes a state transition and is always ignored.
        ///  This means that a SQL string of all whitespace is invalid.
        /// 
        ///  If we compile with SQLITE_OMIT_TRIGGER, all of the computation needed
        ///  to recognize the end of a trigger can be omitted.  All we have to do
        ///  is look for a semicolon that is not part of an string or comment.
        /// </summary>
        /// <param name="zSql"></param>
        /// <returns></returns>
        public static int sqlite3_complete(string zSql)
        {
            int state = 0; /* Current state, using numbers defined in header comment */
            int token; /* Value of the next token */

#if !SQLITE_OMIT_TRIGGER
/* A complex statement machine used to detect the end of a CREATE TRIGGER
** statement.  This is the normal case.
*/
            var trans = new[]
                            {
                                /* Token:                                                */
                                /* State:       **  SEMI  WS  OTHER  EXPLAIN  CREATE  TEMP  TRIGGER  END */
                                /* 0 INVALID: */ new byte[] {1, 0, 2, 3, 4, 2, 2, 2,},
                                                 /* 1   START: */ new byte[] {1, 1, 2, 3, 4, 2, 2, 2,},
                                                 /* 2  NORMAL: */ new byte[] {1, 2, 2, 2, 2, 2, 2, 2,},
                                                 /* 3 EXPLAIN: */ new byte[] {1, 3, 3, 2, 4, 2, 2, 2,},
                                                 /* 4  CREATE: */ new byte[] {1, 4, 2, 2, 2, 4, 5, 2,},
                                                 /* 5 TRIGGER: */ new byte[] {6, 5, 5, 5, 5, 5, 5, 5,},
                                                 /* 6    SEMI: */ new byte[] {6, 6, 5, 5, 5, 5, 5, 7,},
                                                 /* 7     END: */ new byte[] {1, 7, 5, 5, 5, 5, 5, 5,},
                            };
#else
    /* If triggers are not supported by this compile then the statement machine
  ** used to detect the end of a statement is much simplier
  */
      byte[][] trans = new byte[][]   {
     /* Token:           */
     /* State:       **  SEMI  WS  OTHER */
     /* 0 INVALID: */new byte[]  {    1,  0,     2, },
     /* 1   START: */new byte[]  {    1,  1,     2, },
     /* 2  NORMAL: */new byte[] {    1,  2,     2, },
};
#endif
            // * SQLITE_OMIT_TRIGGER */

            int zIdx = 0;
            while (zIdx < zSql.Length)
            {
                switch (zSql[zIdx])
                {
                    case ';':
                        {
                            /* A semicolon */
                            token = tkSEMI;
                            break;
                        }
                    case ' ':
                    case '\r':
                    case '\t':
                    case '\n':
                    case '\f':
                        {
                            /* White space is ignored */
                            token = tkWS;
                            break;
                        }
                    case '/':
                        {
                            /* C-style comments */
                            if (zSql[zIdx + 1] != '*')
                            {
                                token = tkOTHER;
                                break;
                            }
                            zIdx += 2;
                            while (zIdx < zSql.Length && zSql[zIdx] != '*' ||
                                   zIdx < zSql.Length - 1 && zSql[zIdx + 1] != '/')
                            {
                                zIdx++;
                            }
                            if (zIdx == zSql.Length) return 0;
                            zIdx++;
                            token = tkWS;
                            break;
                        }
                    case '-':
                        {
                            /* SQL-style comments from "--" to end of line */
                            if (zSql[zIdx + 1] != '-')
                            {
                                token = tkOTHER;
                                break;
                            }
                            while (zIdx < zSql.Length && zSql[zIdx] != '\n')
                            {
                                zIdx++;
                            }
                            if (zIdx == zSql.Length) return state == 1 ? 1 : 0; //if( *zSql==0 ) return state==1;
                            token = tkWS;
                            break;
                        }
                    case '[':
                        {
                            /* Microsoft-style identifiers in [...] */
                            zIdx++;
                            while (zIdx < zSql.Length && zSql[zIdx] != ']')
                            {
                                zIdx++;
                            }
                            if (zIdx == zSql.Length) return 0;
                            token = tkOTHER;
                            break;
                        }
                    case '`': /* Grave-accent quoted symbols used by MySQL */
                    case '"': /* single- and double-quoted strings */
                    case '\'':
                        {
                            int c = zSql[zIdx];
                            zIdx++;
                            while (zIdx < zSql.Length && zSql[zIdx] != c)
                            {
                                zIdx++;
                            }
                            if (zIdx == zSql.Length) return 0;
                            token = tkOTHER;
                            break;
                        }
                    default:
                        {
#if SQLITE_EBCDIC
        unsigned char c;
#endif
                            if (IdChar((byte) zSql[zIdx]))
                            {
                                /* Keywords and unquoted identifiers */
                                int nId;
                                for (nId = 1; (zIdx + nId) < zSql.Length && IdChar((byte) zSql[zIdx + nId]); nId++)
                                {
                                }
#if  SQLITE_OMIT_TRIGGER
                token = tkOTHER;
#else
                                switch (zSql[zIdx])
                                {
                                    case 'c':
                                    case 'C':
                                        {
                                            if (nId == 6 && Utility.Sqlite3StrNICmp(zSql, zIdx, "create", 6) == 0)
                                            {
                                                token = tkCREATE;
                                            }
                                            else
                                            {
                                                token = tkOTHER;
                                            }
                                            break;
                                        }
                                    case 't':
                                    case 'T':
                                        {
                                            if (nId == 7 && Utility.Sqlite3StrNICmp(zSql, zIdx, "trigger", 7) == 0)
                                            {
                                                token = tkTRIGGER;
                                            }
                                            else if (nId == 4 && Utility.Sqlite3StrNICmp(zSql, zIdx, "temp", 4) == 0)
                                            {
                                                token = tkTEMP;
                                            }
                                            else if (nId == 9 && Utility.Sqlite3StrNICmp(zSql, zIdx, "temporary", 9) == 0)
                                            {
                                                token = tkTEMP;
                                            }
                                            else
                                            {
                                                token = tkOTHER;
                                            }
                                            break;
                                        }
                                    case 'e':
                                    case 'E':
                                        {
                                            if (nId == 3 && Utility.Sqlite3StrNICmp(zSql, zIdx, "end", 3) == 0)
                                            {
                                                token = tkEND;
                                            }
                                            else
#if ! SQLITE_OMIT_EXPLAIN
                                                if (nId == 7 && Utility.Sqlite3StrNICmp(zSql, zIdx, "explain", 7) == 0)
                                                {
                                                    token = tkEXPLAIN;
                                                }
                                                else
#endif
                                                {
                                                    token = tkOTHER;
                                                }
                                            break;
                                        }
                                    default:
                                        {
                                            token = tkOTHER;
                                            break;
                                        }
                                }
#endif
                                // * SQLITE_OMIT_TRIGGER */
                                zIdx += nId - 1;
                            }
                            else
                            {
                                /* Operators and special symbols */
                                token = tkOTHER;
                            }
                            break;
                        }
                }
                state = trans[state][token];
                zIdx++;
            }
            return (state == 1) ? 1 : 0; //return state==1;
        }

#if ! SQLITE_OMIT_UTF16
/*
** This routine is the same as the sqlite3_complete() routine described
** above, except that the parameter is required to be UTF-16 encoded, not
** UTF-8.
*/
int sqlite3_complete16(const void *zSql){
sqlite3_value pVal;
char const *zSql8;
int rc = StatusCode.SQLITE_NOMEM;

#if !SQLITE_OMIT_AUTOINIT
rc = sqlite3_initialize();
if( rc !=0) return rc;
#endif
pVal = sqlite3ValueNew(0);
sqlite3ValueSetStr(pVal, -1, zSql, SQLITE_UTF16NATIVE, Const.SQLITE_STATIC);
zSql8 = sqlite3ValueText(pVal, Const.SQLITE_UTF8);
if( zSql8 ){
rc = sqlite3_complete(zSql8);
}else{
rc = StatusCode.SQLITE_NOMEM;
}
sqlite3ValueFree(pVal);
return Malloc.ApiExit(0, rc);
}
#endif
        // * SQLITE_OMIT_UTF16 */
#endif
        // * SQLITE_OMIT_COMPLETE */
    }
}